前情提要:
昨天又粗略的把事件處理複習一遍,其實跟事件處理密不可分的還有一個角色,對!它就是表單(Form)
處理表單過程中的愛恨糾葛簡直可以寫出一篇長恨歌。react處理的方式可以分成兩類,分別為:
Uncontrolled Components
Controlled Components
先講容易但react不推薦的Uncontrolled Components
吧XDUncontrolled Components
的特色就是表單裡面的data交給DOM自己處理,所以我們需要插手的只有使用ref來從DOM裡面取得表單value。在render mothod裡,藉由設定ref可以取得DOM節點或react element。
以下範例從react複製並說明用法(其實很多範例都是官方挖來的啦,有code才好懂啊!)
建立ref的方法:
React.createRef()
建立ref讓整個component都可以引用refthis.input.current.value
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef(); // 2. 使用React.createRef()
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value); // 3. 取得ref
event.preventDefault(); // 昨天講到的避免瀏覽器默認行為,這邊的默認行為是form submit
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} /> {/* 1. 設定ref屬性 */}
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
看了uncontrolled components後,其實把它想像成一般DOM處理表單就很好理解,透過DOM處理任何事,就像jQueryㄧ樣。
接下來是一舉一動都在掌握中的controlled components,它的特色就是監控使用者輸入行為,把使用者輸入的每一個字都透過state管理,我們來看看controlled components的使用範例:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {textValue: ''}; // 1. 初始化state
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { // 4. 更新textValue
this.setState({textValue: event.target.value});
}
handleSubmit(event) { // 5.
alert('A name was submitted: ' + this.state.textValue);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.textValue} {/* 2. 設定value為state的textValue */}
onChange={this.handleChange} {/* 3. 設定onChange事件處理使用者輸入的字串 */}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
其實不管是uncontrolled components或是controlled components都可以做到即時驗證的功能,關鍵點只在onChange事件監聽。
根據react官網的描述,在大多數的情況推薦使用controlled components,除了<input type="file" />
,它被歸類於永遠都是uncontrolled components
,因為file的value只能由使用者上傳,我們也無法做更動。
不同element也都有不同的value property,以下整理的表格來自Controlled and uncontrolled form inputs in React don't have to be complicated:
Element | Value property | Change callback | New value in the callback |
---|---|---|---|
<input type="text" /> |
value="string" | onChange | event.target.value |
<input type="checkbox" /> |
checked={boolean} | onChange | event.target.checked |
<input type="radio" /> |
checked={boolean} | onChange | event.target.checked |
<textarea /> |
value="string" | onChange | event.target.value |
<select /> |
value="option value" | onChange | event.target.value |
當state要處理很多表單欄位時,如果一個一個欄位setState一定很擾民,因此react提出另一個方法告訴大家,不用擔心!只要把input的name跟state的key值互相對應
,我們就可以透過event取得element的name,而這個name就等於我們在state初始化設定的key值,透過setState就可以輕易把target value對應key值更新儲存。
以實例來說,比如我有一個聯絡人表單,現在很簡單的只有姓名和地址,實際畫面如下:
我想要共用同一個method更新state,程式碼如下:(codepen)
下面的component有幾個重點
共用handleInputChange
這個method[name]
取得name的value並更新state valueclass Contact extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
address: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const { value, name } = target;
this.setState({
[name]: target.value
});
}
render() {
return (
<form>
<label>
姓名
<input
name="name"
type="text"
value={this.state.name}
onChange={this.handleInputChange} />
</label>
<label>
地址
<input
name="address"
type="text"
value={this.state.address}
onChange={this.handleInputChange} />
</label>
<hr/>
<div>
{this.state.name} <br/>
{this.state.address}
</div>
</form>
);
}
}
ReactDOM.render(<Contact />, document.getElementById('app'));
光兩個input element就這麼冗長了,很難想像如果需要填寫其他資料的話,畫面看起來很簡單,背後處理很心酸。這實在不是一件開心的事,更不用說還沒處理到表單驗證這塊。
不哭不哭,市場上有很多解決方案,其中一個就是使用Formik。它的自述是Build forms in React, without tears. XDDDD
Formik幫我們處理三個惱人的地方
在表單驗證這邊,除了自己寫code驗證value外還可以引用第三方library Yup,Yup的描述也蠻爆笑的Dead simple Object schema validation XDDDD
需求:
首先先在開發環境安裝formik和yup
yarn add formik yup
接著在src/components下建立register.js
import React from 'react'
import { withFormik, Form, Field, ErrorMessage } from 'formik'
import * as yup from 'yup'
const errMsg = { // 錯誤訊息的style
color: 'red',
fontSize: '12px',
paddingLeft: '5px'
};
// 表單內容 ▼
const Register = ({
values,
isSubmitting
}) => (
<Form>
<div>
<Field type="email" name="email" placeholder="Email"/>
<ErrorMessage name="email" component="span" style={errMsg}/>
</div>
<div>
<Field type="password" name="password" placeholder="Password"/>
<ErrorMessage name="password" component="span" style={errMsg}/>
</div>
<div>
<label>
<Field type="checkbox" name="rule" checked={values.rule}/>
同意註冊<a href="#">條款</a>
</label>
<ErrorMessage name="rule" component="span" style={errMsg}/>
</div>
<button type="submit" disabled={isSubmitting}>送出</button>
</Form>
)
export default withFormik({
// input 預設值
mapPropsToValues({ email, password, rule }) {
return {
email: email || '',
password: password || '',
rule: rule || false
}
},
// 表單驗證條件&錯誤訊息
validationSchema: yup.object().shape({
email: yup.string().email('Email不符合格式').required('必填'),
password: yup.string().min(6, '密碼至少大於6').required('必填'),
rule: yup.boolean().oneOf([true], '一定要同意!'),
}),
// 點擊送出時
handleSubmit(values, { resetForm, setSubmitting }) {
setTimeout(() => {
resetForm(); //重設表單
setSubmitting(false); //狀態更新(true:傳送中, false:傳送完成)
alert(JSON.stringify(values, null, 2)); //alert values
}, 1000)
}
})(Register)
最後一樣在index.js匯入register component
import React from 'react';
import ReactDOM from 'react-dom';
import Register from './src/components/register';
const App = () => {
return (
<div>
<Register />
</div>
);
};
ReactDOM.render(<App/>, document.getElementById('app'));
實際操作畫面如下:
github傳送門
這次的小練習沒有太多說明,寫完後大概也可以變成新的一篇(?!)
大家有空可以自己玩玩看!
今日總結:
今日完結。